import { cloneVNode, onMounted, onUpdated, PropType, unref } from 'vue'
import { defineComponent, ref, onUnmounted } from 'vue'
import { alignElement, alignPoint } from '../_util/dom-align'
import { findDOMNode } from '../_util/props-util'

import { isSamePoint, monitorResize } from './util'
import type { AlignType, AlignResult, TargetType, TargetPoint } from './interface'
import { MaybeRef, unrefElement, MaybeElement } from '@vueuse/core'

type OnAlign = (source: HTMLElement, result: AlignResult) => void

const alignProps = {
  align: Object as PropType<MaybeRef<AlignType>>,
  target: [Object, Function] as PropType<TargetType>,
  onAlign: Function as PropType<OnAlign>,
  disabled: [Boolean, Object] as PropType<MaybeRef<Boolean>>,
  resizeObservable: { type: [Boolean, Object] as PropType<MaybeRef<Boolean>>, default: true },
}

function getElement(ins: TargetType) {
  const el = unrefElement(ins as MaybeElement)
  return el instanceof HTMLElement ? el : undefined
}

function getPoint(point: TargetType): TargetPoint {
  const el = unrefElement(point as MaybeElement)
  if (el instanceof HTMLElement) {
    const { offsetLeft: pageX, offsetTop: pageY } = getElement(point)
    return { pageX, pageY }
  } else {
    return point as TargetPoint
  }
}

export default defineComponent({
  name: 'Align',
  props: alignProps,
  setup(props, { slots, expose }) {
    const sourceRef = ref()

    function forceAlign() {
      if (unref(props.disabled)) return

      const { onAlign, align } = props
      const source = findDOMNode(sourceRef.value)
      
      if (props.target && source) {
        let result: AlignResult

        const target = getElement(props.target), point = getPoint(props.target)
        targetResizeMonitor.el = target
        targetResizeMonitor.point = point
        
        if (target) {
          result = alignElement(source, target, unref(align))
        } else if (point) {
          result = alignPoint(source, point, unref(align))
        }

        if (onAlign && result) {
          onAlign(source, result)
        }
      }
    }

    const targetResizeMonitor = {
      el: null,
      point: null as TargetPoint,
      cancel: () => {}
    }
    const sourceResizeMonitor = {
      el: null,
      cancel: () => {},
    }

    function goAlign() {
      if (unref(props.disabled)) return
      
      const source = findDOMNode(sourceRef.value)
      if (source !== sourceResizeMonitor.el) {
        sourceResizeMonitor.cancel()
        sourceResizeMonitor.el = source
        // sourceResizeMonitor.value.el = monitorResize(source, forceAlign)
      }
      
      const target = getElement(props.target), point = getPoint(props.target)
      if (targetResizeMonitor.el != target || !isSamePoint(targetResizeMonitor.point, point)) {
        forceAlign()
        if (targetResizeMonitor.el != target) {
          targetResizeMonitor.cancel()
          targetResizeMonitor.el = target
          targetResizeMonitor.point = point
          targetResizeMonitor.cancel = monitorResize(target, forceAlign)
        }
      }
    }

    onUpdated(() => {
      goAlign()
    })
    onMounted(() => {
      goAlign()
    })
    onUnmounted(() => {
      targetResizeMonitor.cancel()
      sourceResizeMonitor.cancel()
    })

    expose({ forceAlign })

    return () => {
      const child = slots.default?.()
      if (child) {
        return cloneVNode(child[0], { ref: sourceRef }, true)
      }
    }
  }
})